/*
 * Contains code for parent scope in order to use wormhole plugin. 
 */
(function($) {
	//endpoint UUID -> contentWindow/Hive object
	var ENDPOINTS = {};
	
	//endpoint -> proxy object
	var ENDPOINT_PROXIES = {};
	
	//Custom exception class
	function WormholeParentException() {}
			
	//Handle method call from an endpoint
	$.pm.bind('wormhole:call', function(request) {
		var endpoint = ENDPOINTS[request.endpointUUID];
		
		if (!endpoint) {
			throw new WormholeParentException;
		}
		
		//Dynamically discover namespaces, in case there are subNSes
		var ns = null;
		if (request.ns != 'jq') {
			ns = discoverNamespace(request.ns);
			handleNormalCall(request, ns, endpoint);
		}
		else {
			handleJqCall(request, endpoint);
		}
	});
	
	//Dynamically discover a namespace, where nsString is "name.space.here"
	function discoverNamespace(nsString) {
		var ns = null;
		var nsSplit = nsString.split('.');
		if (nsSplit.length > 1) {
			var parentNS = window;
			
			for (var c = 0; c < nsSplit.length; c++) {
				ns = nsSplit[c];
				if (parentNS[nsSplit[c]]) {
					parentNS = parentNS[nsSplit[c]];
				}
				else {
					alert('ns fail');
					break;
				}
			}
			
			//Does final namespace mapping.
			ns = parentNS;
		}
		else {
			ns = window[nsString];
		}
		
		return ns;
	}
		
	function handleJqCall(request, endpoint) {
		var selector = request.args[0];
		var method = request.func;
		
		var jqObj = $(endpoint);

		var validSelectors = jqObj.data('selectors');
		var validMethods = jqObj.data('jqMethods');
		var index;
		
		if ($.inArray(selector, validSelectors) != -1) {
			if (validMethods[method]) {
				//Determine whether or not we need args or not (and remove the selector from arg list)
				var methodArgs = (request.args.length > 1) ? request.args.slice(1) : [];
				
				//Handle async vs non-async method.
				if (validMethods[method].async) {
					var callback = asyncCallComplete(request, 'jq', endpoint, null);
					methodArgs.push(callback);
					$.fn[method].apply($(selector), methodArgs);
				}
				else {
					$.fn[method].apply($(selector), methodArgs);
					callComplete(request, 'jq', endpoint, null);					
				}
			}
		}
	}
	
	function handleNormalCall(request, ns, endpoint) {
		if (ns[request.func]) {		
			if (ns[request.func].wormholeExposed) {
				var proxy = ENDPOINT_PROXIES[request.endpointUUID];
				var result = ns[request.func].apply(proxy, request.args);
				callComplete(request, ns, endpoint, result);
			}
			else {
				throw new WormholeParentException;
			}
		}
	}
	
	//Set up a callback to delegate to callComplete. Used for async JQ methods.
	function asyncCallComplete(request, ns, endpoint, result) {
		return function() { 
			callComplete(request, ns, endpoint, result);
		}
	}
	
	function callComplete(request, ns, endpoint, result) {
		//A UUID means that the widget expects a confirmation
		//of the method's completion in order to execute a
		//callback.
		var payload = {
			ns: request.ns,
			method: request.func,
			result: result,
			persistent: request.persistent				
		};
		
		if (request.uuid) {
			payload.uuid = request.uuid;
		}
		
		$.pm({
			target: endpoint.contentWindow,
			type: 'wormhole:callComplete',
			data: payload,
			error: function(err) {
				alert('callComplete error: ' + err.message);
			}
		});
	}
	
	//Create a UUID for an endpoint.
	function createEndpointUUID(endpoint) {
		var uuid = Math.floor(Math.random() * 32768);
		while (ENDPOINTS[uuid] !== undefined) {
			uuid = Math.floor(Math.random() * 32768);
		}
		
		ENDPOINTS[uuid] = endpoint;
		
		return uuid;
	}
	
	function initWormhole(iframe, payload, proxy) {
		$(iframe).data('initialized', true);
		
		var endpointUUID = createEndpointUUID(iframe);

		ENDPOINT_PROXIES[endpointUUID] = proxy;
		
		$.pm({
			target: iframe.contentWindow,
			type: 'wormhole:init',
			data: {
				endpointUUID: endpointUUID,
				payload: payload
			},
			error: function(err) { 
				alert('initWormhole error: ' + err.message); 
			}
		});
	}
	
	function exposeSelectors(iframe, selectorArgs, continueCallback) {
		if (!continueCallback) {
			continueCallback = function() {};
		}
		
		var newSelectors = new Array();
		
		for (i in selectorArgs) {
			newSelectors.push(selectorArgs[i]);
		}

		$.pm({
			target: iframe.contentWindow,
			type: 'wormhole:selectors',
			data: { 
				selectors: newSelectors 
			},
			success: continueCallback
		});
		
		//Update safe selectors list on this end.
		var currSelectors = $(iframe).data('selectors');
		$(iframe).data('selectors', currSelectors.concat(newSelectors));
	}
	
	function exposeMethods(iframe, jqMethodArgs, continueCallback) {
		if (!continueCallback) {
			continueCallback = function() {};
		}
		
		var newMethods = {};
		var newMethodList = [];
		
		for (i in jqMethodArgs) {
			//: indicates this method requires/accepts an async callback
			//as its last parameter. remove the :
			if (jqMethodArgs[i].indexOf(':') == 0) {
				var methodName = jqMethodArgs[i].substring(1);
				newMethods[methodName] = {
					async: true,
					eventMethod: false
				};
			}
			//~ indicates this method is an event method.
			else if (jqMethodArgs[i].indexOf('~') == 0) {
				var methodName = jqMethodArgs[i].substring(1);
				newMethods[methodName] = {
					async: true,
					eventMethod: true
				};				
			}
			//regular old method.
			else {
				newMethods[jqMethodArgs[i]] = {
					async: false,
					eventMethod: false
				};
			}

			newMethodList.push(jqMethodArgs[i]);
		}
		
		//Tell widget to update its list of allowed methods.
		$.pm({
			target: iframe.contentWindow,
			type: 'wormhole:jqMethods',
			data: { 
				methods: newMethodList 
			},
			success: continueCallback
		});
		
		//Update safe selectors list on this end.
		var currMethods = $(iframe).data('jqMethods');
		$.extend(currMethods, newMethods);
		$(iframe).data('jqMethods', currMethods);
	}
	
	//Call on iframe element or web worker to send an init message to it
	$.fn.wormhole = function() {
		var wormholeArgs = arguments;
		
		return this.each(function() {
			if ($(this).is('iframe')) {
				
				var initialized = $(this).data('initialized');
				
				//Initialize data for the element.
				if (!initialized) {
					$(this).data('selectors', new Array());
					$(this).data('jqMethods', {});
				}
				
				//if we have an argument, expose safe selectors and methods,
				//and delay initialization until end. Otherwise just
				//init immediately.
				if (wormholeArgs.length == 1) {
					var selectorArgs = wormholeArgs[0].selectors;
					var jqMethodArgs = wormholeArgs[0].methods;
					var proxy = wormholeArgs[0].proxy;
					var payload = wormholeArgs[0].payload;
					
										
					var thisIframe = this;
					
					exposeSelectors(thisIframe, selectorArgs, function() {
						exposeMethods(thisIframe, jqMethodArgs, function() {
							if (!initialized) {
								initWormhole(thisIframe, payload, proxy);
							}
						});
					});
				}
				else {
					if (!initialized) {
						initWormhole(this, undefined, proxy);
					}
				}
			}
		});
	}
	
	$.expose = function(func) {
		func.wormholeExposed = true;
	}
	
	$.exposeAll = function(ns) {
		for (prop in ns) {
			if (ns[prop] instanceof Function) {
				$.expose(ns[prop]);
			}
		}		
	}
	
})(jQuery);